package org.tbjd.client.states;

import java.util.Date;

import org.tbjd.client.beans.Resource;
import org.tbjd.client.beans.Task;
import org.tbjd.client.constants.TimeConstants;
import org.tbjd.client.exceptions.DateManagerBeginDateException;
import org.tbjd.client.exceptions.DateManagerDurationException;
import org.tbjd.client.exceptions.DateManagerEffortException;
import org.tbjd.client.exceptions.DateManagerEndDateException;
import org.tbjd.client.exceptions.DateManagerProgressException;

abstract public class AbstractTaskState {

	private AbstractTaskDurationState durationState = null;
	protected boolean startASAP = false;

	public final void beginDateChangedTemplate(Task task, Date newBeginDate) throws DateManagerBeginDateException {
		if (task.getState() instanceof StateEndFrozenASAP || task.getState() instanceof StateEndFrozenSPEC) {
			if (newBeginDate.compareTo(task.getEndDate()) > 0) {
				throw new DateManagerBeginDateException();
			}
		}

		beginDateChanged(task, newBeginDate);
	}

	public final void endDateChangedTemplate(Task task, Date newEndDate) throws DateManagerEndDateException {
		if (task.getState() instanceof StateBeginFrozenASAP || task.getState() instanceof StateBeginFrozenSPEC) {
			if (newEndDate.compareTo(task.getBeginDate()) <= 0) {
				throw new DateManagerEndDateException();
			}
		}

		endDateChanged(task, newEndDate);
	}

	public final void durationChangedTemplate(Task task, float newDuration) throws DateManagerDurationException {
		if (newDuration < 1) {
			throw new DateManagerDurationException();
		}

		durationChanged(task, newDuration);

		// if task has zero or one resource at 100%, then update effort with same value as duration
		if (task.getResources() != null) {
			if (task.getResources().size() == 0 || (task.getResources().size() == 1 && ((Resource) task.getResources().get(0)).getRatio() == 100)) {
				task.setEffort(task.getDuration());
			}
		}
	}

	public final void progressChangedTemplate(Task task, int newProgress) throws DateManagerProgressException {
		if (newProgress < 0) {
			throw new DateManagerProgressException();
		}

		progressChanged(task, newProgress);

	}
	
	public final void effortChangedTemplate(Task task, float newEffort) throws DateManagerEffortException {
		if (newEffort < 1) {
			throw new DateManagerEffortException();
		}

		effortChanged(task, newEffort);
	}

	public final void resourceChangedTemplate(Task task) {
		resourceChanged(task);
	}

	public abstract void beginDateChanged(Task task, Date newBeginDate) throws DateManagerBeginDateException;

	public abstract void endDateChanged(Task task, Date newEndDate) throws DateManagerEndDateException;

	public abstract void durationChanged(Task task, float newDuration) throws DateManagerDurationException;
	
	public abstract void progressChanged(Task task, int newProgress) throws DateManagerProgressException;

	public abstract void effortChanged(Task task, float newEffort) throws DateManagerEffortException;

	/**
	 * Used when a Resource is added/removed or when its ratio changed
	 * 
	 * @param task
	 */
	public abstract void resourceChanged(Task task); // the exception is treated in the ResourceManager

	/**
	 * Shift the begin and end date according to the offset
	 * 
	 * @param originalBeginDate
	 * @param offset
	 * @return true if the task has been shifted
	 */
	public void shift(Task task, long offset) {
		task.setBeginDate(new Date(task.getBeginDate().getTime() + offset));
		task.setEndDate(new Date(task.getEndDate().getTime() + offset));
	}

	/**
	 * Formulas : BeginDate = EndDate - Duration
	 * 
	 * @param endDate
	 * @param newDuration
	 * @return
	 */
	protected Date getBeginDate(Date endDate, float newDuration) {
		return new Date((long) (endDate.getTime() - (long) (newDuration * TimeConstants.HOUR_IN_MILLIS)));
	}

	/**
	 * Formulas : BeginDate = EndDate - Duration
	 * 
	 * @param endDate
	 * @param newDuration
	 * @return
	 */
	protected Date getBeginDate(Task task, Date newEndDate) {
		return new Date(newEndDate.getTime() - duration2milis(task.getDuration()));
	}

	/**
	 * Formulas : EndDate = BeginDate + Duration
	 * 
	 * @param newBeginDate
	 * @return new EndDate
	 */
	protected Date getEndDate(Task task, Date newBeginDate) {
		return new Date(newBeginDate.getTime() + duration2milis(task.getDuration()));
	}

	/**
	 * Formulas : EndDate = BeginDate + Duration
	 * 
	 * @param newBeginDate
	 * @return new EndDate
	 */
	protected Date getEndDate(Date beginDate, float newDuration) {
		return new Date((long) (beginDate.getTime() + (long) (newDuration * TimeConstants.HOUR_IN_MILLIS)));
	}

	/**
	 * Converts duration in hours into milliseconds for easier dates manipulation
	 * 
	 * @param duration
	 *            in hours
	 * @return duration in milliseconds
	 */
	private long duration2milis(float duration) {
		return (long) (duration * TimeConstants.HOUR_IN_MILLIS);
	}

	/**
	 * <p>
	 * Set the chaining mode for this task
	 * </p>
	 * 
	 * @return true if in ASAP mode
	 */
	public boolean isASAPChainingMode() {
		return startASAP;
	}

	public void setASAPChainingMode(boolean chainingMode) {
		startASAP = chainingMode;
	}

	public AbstractTaskDurationState getDurationModeState() {
		return durationState;
	}

	public void setDurationState(AbstractTaskDurationState durationState) {
		this.durationState = durationState;
	}

}
